package cn.xuqiudong.attachment.helper;

import cn.xuqiudong.attachment.autoconfigure.AttachmentProperties;
import cn.xuqiudong.attachment.model.Attachment;
import cn.xuqiudong.attachment.model.AttachmentOss;
import cn.xuqiudong.common.base.model.BaseResponse;
import cn.xuqiudong.common.util.JsonUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.CopyObjectResult;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.function.Consumer;

/**
 * 描述: 阿里Oss 工具类
 * @author Vic.xu
 * @since 2022-11-15 9:54
 */
public class AliOssHelper {

    private final static Logger LOGGER = LoggerFactory.getLogger(AliOssHelper.class);

    private AttachmentProperties attachmentProperties;

    private AttachmentProperties.AliOss aliOss;

    public AliOssHelper(AttachmentProperties attachmentProperties) {
        this.attachmentProperties = attachmentProperties;
        this.aliOss = attachmentProperties.getAliOss();
    }

    /**
     * 获取ali oss client
     * @return OSS client
     */
    public OSS getOssClient() {
        return new OSSClientBuilder().build(aliOss.getEndpoint(), aliOss.getAccessKeyId(), aliOss.getAccessKeySecret());
    }

    public void closeClient(OSS ossClient) {
        if (ossClient != null) {
            ossClient.shutdown();
        }
    }

    /**
     * 构造ObjectName: Object完整路径，完整路径中不能包含Bucket名称，例如exampleDir/exampleObject.txt
     * 此处的构造方式为：id + / + fileName   =  123/test.png
     * @param attachment Attachment
     * @return objectName
     */
    public String buildObjectName(Attachment attachment) {
        return attachment.getId() + "/" + attachment.getFilename();
    }

    /**
     * 构建备份的文件名
     * @param id id
     * @param fileName new file name
     * @return objectName
     */
    public String buildBackupObjectName(Integer id, String fileName) {
        return id + "/backup/" + fileName;
    }


    public BaseResponse<AttachmentOss> upload(Attachment attachment) {
        if (attachment == null) {
            return BaseResponse.error("attachment is null");
        }
        File file = attachment.getRealFile();
        if (file == null || !file.exists()) {
            return BaseResponse.error("The file corresponding to the attachment does not exist");
        }
        String objectName = this.buildObjectName(attachment);
        boolean result = this.realUpload(file, objectName);

        // will remove original file  if it exists
        boolean removeOrigin = aliOss.isRemoveOrigin();
        if (removeOrigin) {
            FileUtils.deleteQuietly(file);
        }
        AttachmentOss attachmentOss = new AttachmentOss();
        attachmentOss.setAttachmentId(attachment.getId());
        attachmentOss.setObjectName(objectName);
        attachmentOss.setOriginRemoved(removeOrigin);
        attachmentOss.setResult(result);
        return BaseResponse.success(attachmentOss);

    }


    public boolean realUpload(File file, String objectName) {

        // 填写Object完整路径，完整路径中不能包含Bucket名称，例如exampledir/exampleobject.txt。

        OSS ossClient = null;
        try {
            ossClient = getOssClient();
            InputStream bi = new FileInputStream(file);
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(aliOss.getBucketName(), objectName, bi);

            // 如果需要上传时设置存储类型和访问权限，请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            /* 上传： 正常返回的结果集如下：
            {
              "requestId" : "6374599CF947FB34302FD004",
              "clientCRC" : 862931638539860525,
              "serverCRC" : 862931638539860525,
              "etag" : "4CF3F68D9ECAE24878B897882CD865DE"
            }
             */
            PutObjectResult result = ossClient.putObject(putObjectRequest);
            LOGGER.info("objectName = {} : ali oss upload result = {}", objectName, JsonUtil.toJson(result));
            return result != null && StringUtils.isNotBlank(result.getRequestId());

        } catch (Exception e) {
            LOGGER.error(" Failed to upload files to ali oss， objectName = " + objectName, e);
        } finally {
            closeClient(ossClient);
        }
        return false;
    }


    /**
     * 获取预授权url  自行确保附件已经上传
     * @param attachment Attachment
     * @return url
     */
    public String generatePreSignedUrl(Attachment attachment) {
        return generatePreSignedUrl(buildObjectName(attachment));

    }

    /**
     * 获取预授权url  自行确保附件已经上传
     * @param objectName Object完整路径，完整路径中不能包含Bucket名称，例如exampleDir/exampleObject.txt
     * @return url
     */
    public String generatePreSignedUrl(String objectName) {
        OSS ossClient = null;
        try {
            long expiration = System.currentTimeMillis() + (aliOss.getPreSignedUrlExpiration() * 1000);
            ossClient = getOssClient();
            URL url = ossClient.generatePresignedUrl(aliOss.getBucketName(), objectName, new Date(expiration));
            return url.toString();
        } catch (Exception e) {
            LOGGER.error("generate ali oss  preSignedUrl error ", e);
            return null;
        } finally {
            closeClient(ossClient);
        }

    }

    /**
     * 从ali oss拉取文件流
     * @param objectName Object完整路径，完整路径中不能包含Bucket名称，例如exampleDir/exampleObject.txt
     * @param consumer 消费者
     * @return is successfully
     */
    public boolean consumerOssStream(String objectName, Consumer<InputStream> consumer) {
        OSS ossClient = null;
        try {
            ossClient = getOssClient();
            OSSObject ossObject = ossClient.getObject(aliOss.getBucketName(), objectName);
            consumer.accept(ossObject.getObjectContent());
            return true;
        } catch (Exception e) {
            LOGGER.error(" consumer Oss Stream  error ", e);
            return false;
        } finally {
            closeClient(ossClient);
        }
    }

    /**
     * 重命名文件
     * OSS不支持直接对文件（Object）进行重命名。如果您需要在同一个Bucket内对Object进行重命名，您可以通过CopyObject接口将源Object拷贝至目标Object，然后通过DeleteObject接口删除源Object。
     * @link https://help.aliyun.com/zh/oss/developer-reference/rename-objects?spm=a2c4g.11186623.0.i6
     */
    public boolean rename(Attachment attachment, String newFileName) {
        if (attachment == null) {
            return false;
        }
        OSS ossClient = null;
        try {
            ossClient = getOssClient();
            String bucketName = aliOss.getBucketName();
            String originalObjectName = buildObjectName(attachment);
            String backupObjectName = buildBackupObjectName(attachment.getId(), newFileName);
            //copy to same bucket
            CopyObjectResult copyObjectResult = ossClient.copyObject(bucketName, originalObjectName, bucketName, backupObjectName);
            LOGGER.info("attachment {}  rename to {} , result: {}", originalObjectName, backupObjectName, JsonUtil.toJson(copyObjectResult));
            //delete original file
            ossClient.deleteObject(bucketName, originalObjectName);
            return copyObjectResult != null && StringUtils.isNotBlank(copyObjectResult.getRequestId());
        } catch (Exception e) {
            LOGGER.error(" rename attachment [" + attachment.getId() + "] error ", e);
        } finally {
            closeClient(ossClient);
        }
        return false;
    }

}
